package com.popcap.ant;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;


/**
 * GameResourcesTask 
 *
 * Description: An Ant task for creating resource files using the information from an XML game descriptor. 
 * <br/> 
 *
 * @author Plamen Dragozov
 * 
 */
public class JMergeTask extends Task
{
	//Task vars
	private String srcDir;
	private String fileNames;
	private String outputFile;
	private String outputDir;
	
	
	
	//files to process
	String[] files;
    String[] filePriorityNames;
    int[] filePriorityNumbers;

	StringBuffer m_strOutput = new StringBuffer();
	
	String m_strPre;
	String m_strMatch;
	String m_strPost;
	String m_strClass;
	String m_strFunction;
	String m_strGlobalClassName;
	
	Vector m_vImplements = new Vector();
	Vector m_vImports = new Vector();
	Vector m_vClassNames = new Vector();
	Hashtable m_hAdjustedValues = new Hashtable();
	Hashtable m_hGlobalAdjustedValues = new Hashtable();
	
	
	
	StringBuffer m_strProtectedCode = new StringBuffer();
	String m_strStaticBlock;
	ArrayList m_StaticBlockList = new ArrayList();
	//	 The method executing the task	
	public void execute() throws BuildException
	{
		boolean firstClass = true;
		try{
			//System.out.println("GABE IS THE BEST");
			
            
			//Get all the files in the directory
            files = new File(srcDir).list();
            
            //Sort Files (this is needed to remove illegal forward references)
            for(int j = 0; j < filePriorityNames.length; j++)
            {
                for(int i = 0; i < files.length; i++)
                {
                    if(files[i].equals(filePriorityNames[j]))
                    {
                        String temp = files[filePriorityNumbers[j]]; 
                        files[filePriorityNumbers[j]] = filePriorityNames[j];
                        files[i] = temp;
                    }
                }
            }
            
			//Preprocessing 
			for(int i = 0; i < files.length; i++)
			{
				System.out.println("PreProcessing File: "+files[i]+":");
				String file = getFile(srcDir+files[i]);
				if(isLibraryFile(file))
				{
					file = removeComments(file);
					getImports(file);
					getImplements(file);
				}
			}
			
//			System.out.println(m_vImplements);
			System.out.println("");
						
			//write the imports
			for(int j = 0; j < m_vImports.size(); j++) 
			{				
				m_strOutput.append(m_vImports.get(j)+"\n");					
			}
			
			
			
			m_StaticBlockList.clear();
			//Processing
			System.out.println("\nProcessing Library Files:");
			for(int i = 0; i < files.length; i++)
			{				
				String file = getFile(srcDir+files[i]);
				
				if(isLibraryFile(file))
				{
					System.out.println("Processing File: "+files[i]+":");	
					file = removeStaticBlock(file);
					file = removeProtectedCode(file);
					file = removeComments(file);
					
					//finds the class in this file
					getClass(file);
					
					if(firstClass)
					{
						writeClassHeader(m_strOutput);
						firstClass = false;
					}
					
					//Get class block
					file = getBlock(file);					
					file = file.substring(1, file.length()-1);//remove braces
					
					//Get functions
					String originalFile = file;					
					System.gc();
					while(getFunction(file) != null)
					{						
						//handle everything before the function
						getClassVariables(m_strPre);
						getFunctionDefinition(m_strMatch);
						getBlock(m_strPost);
						//fileOutput.append(m_strMatch);
						file = m_strPost;
						//System.out.println(file);
					}
					
					//Re write protected code.
					file = originalFile+m_strProtectedCode;
					
					//Handle Variable and Function name ajustmets
					Iterator itr = m_hAdjustedValues.keySet().iterator();					
					while(itr.hasNext())
					{						
						String value = (String)itr.next();					
						file = file.replaceAll("([^\\w\\.])"+value+"([^\\w])", "$1"+(String)m_hAdjustedValues.get(value)+"$2");
						if(m_strStaticBlock != null)
						{
							m_strStaticBlock = m_strStaticBlock.replaceAll("([^\\w\\.])"+value+"([^\\w])", "$1"+(String)m_hAdjustedValues.get(value)+"$2");
						}
					}
					if(m_strStaticBlock != null)
					{
						int index = m_strStaticBlock.indexOf("static");
						m_strStaticBlock = m_strStaticBlock.substring(0, index) + m_strStaticBlock.substring(index + 6);
						m_StaticBlockList.add(m_strStaticBlock);
					}
					
					m_hAdjustedValues.clear();
					
					m_strOutput.append("\n//"+m_strClass+"_java\n");

					m_strOutput.append(file);					
				}				
			}
			if(m_StaticBlockList.size() > 0)
			{
				//create a static block
				m_strOutput.append("\n//STATIC BLOCKS MERGED");
				m_strOutput.append("\nstatic\n{");
				Iterator it = m_StaticBlockList.iterator();
				while(it.hasNext())
				{
					m_strOutput.append("\n");
					m_strOutput.append(it.next());
				}
				m_strOutput.append("\n}");	
				m_strOutput.append("\n//END OF STATIC BLOCKS MERGED");
			}
			
			m_strOutput.append("\n}");
			
			//Handle global Variable and Function name ajustmets in main file
			Iterator itr = m_hGlobalAdjustedValues.keySet().iterator();
			String file = m_strOutput.toString();
			System.out.println("\nProcessing: Second Pass");
			while(itr.hasNext())
			{
				String value = (String)itr.next();
				file = file.replaceAll("([^\\w])"+value+"([^\\w])", "$1"+(String)m_hGlobalAdjustedValues.get(value)+"$2");
			}
			
			
			System.out.print("Processing: Final Pass\n");
			//Handle left over old class instances... (probably in an implemented interface)
			itr = m_vClassNames.iterator();
			while(itr.hasNext())
			{
				String value = (String)itr.next();
				file = file.replaceAll("([^\\w])"+value+"\\.", "$1"+m_strGlobalClassName+".");
			}
			
			
			//Write back to output buffer
			m_strOutput = new StringBuffer(file);

			
			
			//Handle non library files
			System.out.println("\nProcessing Non-Library Files:");
			for(int i = 0; i < files.length; i++)
			{				
				file = getFile(srcDir+files[i]);

				if(!isLibraryFile(file))
				{
					System.out.println("Processing File: "+files[i]+":");
					//Handle golbal Variable and functions in non-library files
					itr = m_hGlobalAdjustedValues.keySet().iterator();		
					while(itr.hasNext())
					{
						String value = (String)itr.next();						
						file = file.replaceAll("([^\\w])"+value+"([^\\w])", "$1"+m_strGlobalClassName+"."+(String)m_hGlobalAdjustedValues.get(value)+"$2");	
					}
				
				
					//Handle left over old class instances... (probably in an implemented interface)
					itr = m_vClassNames.iterator();
					while(itr.hasNext())
					{
						String value = (String)itr.next();
						file = file.replaceAll(value+"\\.", m_strGlobalClassName+".");
					}
					//Write out ouput file
					FileWriter os = new FileWriter(outputDir+files[i]);
					os.write(file);
					os.close();
				}
			}
			
			System.out.println("");
			//Write out ouput file
			FileWriter os = new FileWriter(outputDir+outputFile);
			os.write(m_strOutput.toString());
			os.close();
		}
		catch (Exception e)
		{
			System.out.println("YOU HAVE A PROBLEM WITH YOUR TOOL:");
			System.out.println("\t"+e);		
			e.printStackTrace();
		}
	}
	
	
	
	/**
	 * Creates a string from a file
	 * @param filename the name of the file
	 * @return
	 */
	String getFile(String filename) throws IOException
	{
		BufferedReader br = new BufferedReader(new FileReader(filename)); 
		StringBuffer sb = new StringBuffer();
		while(true)
		{
			String line = br.readLine();
			if(line != null)
			{
				sb.append(line);
				sb.append("\n");
			}
			else
			{
				break;
			}
		}
		br.close();
		return sb.toString();
	}
	
	/*
	 * 
	 */
	String getClass(String file)
	{
		//find a class (assumes all class modifiers before "class" are on the same line)
		String classDeclaration = "^.*class\\s*(\\w*)(.|\n)*?\\{";
		Pattern classDeclarationPattern = Pattern.compile(classDeclaration, Pattern.MULTILINE);
		Matcher match = classDeclarationPattern.matcher(file);
		if(match.find())
		{
			m_strPre  = file.substring(0, match.start());
			m_strPost = file.substring(match.end());
			m_strMatch = match.group();
			m_strClass = match.group(1);
		}
		
		if(!m_vClassNames.contains(m_strClass))
		{
			m_vClassNames.add(m_strClass);
		}
	
		return m_strMatch;
	}
	
	String getFunction(String file)
	{
		//find a function (assumes all  modifiers before "class" are on the same line)
		String functionDeclaration = "^[^=;]*?(\\w+) *\\((.*?)\\)\\s*?\\{";
		Pattern functionDeclarationPattern = Pattern.compile(functionDeclaration, Pattern.MULTILINE|Pattern.DOTALL);
		Matcher match = functionDeclarationPattern.matcher(file);
		
		if(match.find())
		{
			
			m_strPre  = file.substring(0, match.start());
			m_strPost = "{"+file.substring(match.end());
			m_strMatch = match.group().substring(0, match.group().length()-1);
			m_strFunction = match.group(1);
		}
		else
		{			
			m_strMatch = null;
		}		
		//System.out.println("funciton >>"+m_strMatch);
		return m_strMatch;
	}
	
	String getBlock(String file)
	{
		String original = file;
		
		int numparens = 0;
		StringBuffer block = new StringBuffer();
		String preBlock = "";
		String postBlock = "";
		
		
		int beginingOfBlock = 0;
		int endOfBlock = 0;
//		 match an open paren or colse paren
		String blockString = "(\\{|\\})";
		Pattern blockPattern = Pattern.compile(blockString, Pattern.DOTALL);
		Matcher blockMatcher = blockPattern.matcher(file);
		do
		{	
			if(blockMatcher.find())
			{
				//copy these so they don't get over written in the next check
				int start = blockMatcher.start();
				String match = blockMatcher.group();
				int end = blockMatcher.end();
				
				
				//check if i found the open or close (might be a better way to to do this)
				if(Pattern.matches("\\{", match))
				{
					if(numparens == 0)
					{
						beginingOfBlock = start;
						preBlock = file.substring(0, start);
					}
					numparens++;
				}
				else
				{
					numparens--;
					if(numparens == 0)
					{
						endOfBlock = end;
						postBlock = file.substring(end);
					}											
				}			
			}
			else
			{			
				//parens didn't match
				System.out.println("ERROR: Braces didn't match in getBlock");
				preBlock = "";
				postBlock = "";
				block = new StringBuffer("");
				numparens = 0;
			}				
		}
		while(numparens != 0);
		
		m_strPre  = preBlock;
		m_strPost = postBlock;
		m_strMatch = file.substring(beginingOfBlock, endOfBlock);
	
		return m_strMatch;
	}

	
	boolean isLibraryFile(String file)
	{
		//System.out.println(file);
		String lib = "//!LIBRARY";
		Pattern libPat = Pattern.compile(lib);
		Matcher libMatch = libPat.matcher(file);
		return libMatch.find();
	}
	
	/**
	 * Adds all implements strings to the m_implemets vector.  With out duplication
	 * @param file
	 */
	String getImplements(String file)
	{
		//System.out.println("HMMMM");
		String implementStr = "implements(.*?)\\{";
		Pattern impPattern = Pattern.compile(implementStr, Pattern.MULTILINE|Pattern.DOTALL);
		
		StringBuffer newFile = new StringBuffer();
		int end = 0;
			
	//	System.out.println("IS A LIBRARY FILE");
		String match = getClass(file);
		Matcher impMatcher = impPattern.matcher(match);
//			/System.out.println(match+"\n");
		
		if(impMatcher.find())
		{
			
			match = impMatcher.group(1);
			String wordString = "\\w+";
			
			Pattern wordPattern = Pattern.compile(wordString);
			Matcher wordMatch = wordPattern.matcher(match);

			//System.out.println("found these"+match);
			
			while(wordMatch.find())
			{
				String word = wordMatch.group();
				//System.out.println("parsed these "+word);
				if(!m_vImplements.contains(word))
				{			
					//System.out.println("added");
					m_vImplements.add(word);
				}
			
			}
				
			newFile.append(file.substring(0, impMatcher.start()));
			newFile.append(file.substring(impMatcher.end()));			
			return newFile.toString();	
		}	
		return file;	
	}
	
	/**
	 * Adds all import strings to the m_import vector.  With out duplication
	 * @param file
	 */
	void getImports(String file)
	{
		//finds an import
		String importStr = "import.*;";
		Pattern importPattern = Pattern.compile(importStr);
		Matcher importMatcher = importPattern.matcher(file);			
		while(importMatcher.find())
		{
			//System.out.println(importMatcher.group()+"\n");
			if(!m_vImports.contains(importMatcher.group()))
			{
				m_vImports.add(importMatcher.group());
			}
		}		
	}
	
	/**
	 * find protected code and remove it save it for later
	 * @param file
	 * @return
	 */
	String removeProtectedCode(String file)
	{		
		StringBuffer newFile = new StringBuffer(); 
		m_strProtectedCode = new StringBuffer();
		String protectedString = "//!PROTECTED(.*?)//!END_PROTECTED";
		Pattern protectedPattern = Pattern.compile(protectedString, Pattern.DOTALL);
		Matcher protectedMatch = protectedPattern.matcher(file);
		if(protectedMatch.find())
		{
			m_strProtectedCode.append(protectedMatch.group(1));//the match
			newFile.append(file.substring(0, protectedMatch.start()));//before the match
			newFile.append(file.substring(protectedMatch.end()));//after the match
			//System.out.println(protectedMatch.group(1));
			return newFile.toString();
		}
		return file;
	}
	
	/**
	 * find protected code and remove it save it for later
	 * @param file
	 * @return
	 */
	String removeStaticBlock(String file)
	{		
		m_strStaticBlock = null;
		StringBuffer newFile = new StringBuffer(); 
		String protectedString = "//!STATIC_BLOCK(.*?)//!END_STATIC_BLOCK";
		Pattern protectedPattern = Pattern.compile(protectedString, Pattern.DOTALL);
		Matcher protectedMatch = protectedPattern.matcher(file);
		if(protectedMatch.find())
		{
			m_strStaticBlock = protectedMatch.group(1);//the match
			newFile.append(file.substring(0, protectedMatch.start()));//before the match
			newFile.append(file.substring(protectedMatch.end()));//after the match
			//System.out.println(protectedMatch.group(1));
			return newFile.toString();
		}
		return file;
	}
	
	String removeComments(String file)
	{
		StringBuffer newFile = new StringBuffer(); 
		String commentString = "((/\\*.*?\\*/)|(//.*?\n))";
		Pattern commentPattern = Pattern.compile(commentString, Pattern.DOTALL);
		Matcher commentMatch = commentPattern.matcher(file);
		int end = 0;
		while(commentMatch.find())
		{	
			//System.out.println("found comment\n");
			//System.out.println(commentMatch.group());
						
			newFile.append("\n"+file.substring(end, commentMatch.start()));//before the match
			end = commentMatch.end();
		}
		newFile.append("\n"+file.substring(end));//after the match			
		return newFile.toString();
		
	
	}
	
	void writeClassHeader(StringBuffer output)
	{	
		
		m_strMatch = getImplements(m_strMatch);
		m_strMatch = m_strMatch.replaceAll("\\{", "");
		//System.out.println(m_strMatch);
		
		output.append("\n");
		output.append(m_strMatch);
		m_strGlobalClassName = m_strClass;
		for(int i = 0; i < m_vImplements.size(); i++)
		{
			if(i == 0)
			{
				output.append("\timplements ");
			}
			output.append((String)m_vImplements.get(i));
			if(i < m_vImplements.size()-1)
			{
				output.append(" ,");
			}
		}
		output.append("\n{\n");		
	}
	
	void getClassVariables(String file)
	{
		//get non function experessions
		String expressionStr = ".*?;";
		Pattern expressionPattern = Pattern.compile(expressionStr, Pattern.DOTALL);
		Matcher expMatcher = expressionPattern.matcher(file);
		
		while(expMatcher.find()) //match every thing up to a semicolon
		{
			String expression = expMatcher.group();
			//System.out.println("Get Variable Names from: "+expression+"\n");
		
			String varName = null;
			//get the name of the variable
								 
			String initExprStr = "[^=]*\\s(\\w+)(\\s|\\[|\\])*=?[^=]*?;";//Iniailized variable expression 
			Pattern initExprPattern = Pattern.compile(initExprStr, Pattern.DOTALL);
			Matcher initExprMatcher = initExprPattern.matcher(expression);
			if(initExprMatcher.find())
			{
				varName = initExprMatcher.group(1);
				//System.out.println("varname1 = "+varName+"\n");
			}
			
			//write variables
			String newVarName = "";
			String newExpression = "";
			if(varName != null)
			{	
				int varNameStart = expression.indexOf(varName);
				newVarName = m_strClass+"_"+varName;
				
				expression = expression.substring(0, varNameStart)+newVarName+expression.substring(varNameStart+varName.length());
				//output.append(expression);
				
				String globalVarName = m_strClass+"."+varName;

				m_hAdjustedValues.put(varName, newVarName);
				m_hGlobalAdjustedValues.put(globalVarName, newVarName);				
			}
			else
			{
				System.out.println("ERROR: Failed to match an expression");
			}
		}
	}
	
	
	void getFunctionDefinition(String file)
	{
		int funcNameStart = file.indexOf(m_strFunction);
		if(funcNameStart != -1)
		{	
			//System.out.println("fuction name: "m_strFunction+"\n");			
			
			String newFuctionName = m_strClass+"_"+m_strFunction;

			String functionDef  = file.substring(0, funcNameStart)+newFuctionName+m_strMatch.substring(funcNameStart+m_strFunction.length());
			//output.append(functionDef);

			String globalFunctionName = m_strClass+"."+m_strFunction;
			
			m_hAdjustedValues.put(m_strFunction , newFuctionName);
			m_hGlobalAdjustedValues.put(globalFunctionName, newFuctionName);
		}
	}
	

	
	public void setSrcDir(String str)
	{
		this.srcDir = str;
	}
	
	
	public void setfileNames(String str)
	{
		StringTokenizer tok = new StringTokenizer(str, ",");
		files = new String[tok.countTokens()];
		for(int i = 0; i < files.length; i++)
		{
			files[i] = tok.nextToken();
		}
		
	}
	
    public void setPriority(String str)
    {
        StringTokenizer tok = new StringTokenizer(str, ",");        
        filePriorityNames = new String[tok.countTokens()];
        filePriorityNumbers = new int[tok.countTokens()];
        
        for(int i = 0; i < filePriorityNames.length; i++)
        {
            StringTokenizer tok2 = new StringTokenizer(tok.nextToken(), ":");

            filePriorityNames[i] = tok2.nextToken();
            filePriorityNumbers[i] = Integer.parseInt(tok2.nextToken());
        }
        
    }
    
	public void setOutputFile(String str)
	{
		this.outputFile = str;
	}
	
	public void setOutputDir(String str)
	{
		this.outputDir = str;
	}
	
}

